package org.study.mvc.core;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.study.mvc.annotation.Controller;
import org.study.mvc.annotation.RequestMapping;
import org.study.mvc.resolver.ViewResolver;


import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * spring mvc 实际的上是通过servlet核心来转发,所有的请求都汇聚到DispatcherServlet对象这里，在通过我们配置的controller，通过代理的方式来执行，具体到流程如下
 * 1,在web.xml里配置DispatcherServlet中央处理器类路径,并且配置初始化参数(主要是spring-mvc.xml文件),还需要配置一个拦截的路径（什么请求需要拦截经中央处理器）
 * 2，解析web.xml文件的内容,并把xml解析为一个map数据结构(主要是扫描控制器的路径和我们在控制器中写的标签)
 * 3,前端过来一个请求，我们只需要去map中按照一定的规则找是否存在key(实际解析的map的key就是我们controller的RequestMapping)
 * 4,如果能找到，则通过代理反射来执行，如过没有找到就返回404
 * 5,处理完控制器的方法之后,需要我们返回前端数据（这里数据可能是一个jsp页面，如果不了解jsp页面的本质,可以去找一个，现在都用前后端分离，一般返回一个json）
 */
public class DispatcherServlet extends HttpServlet {
    //IOC容器，保存Controller实例对象
    private static final ConcurrentHashMap<String, Object> mvcBeanMap = new ConcurrentHashMap<String, Object>();
    //保存handler映射
    private static final ConcurrentHashMap<String, Method> handlerMapping = new ConcurrentHashMap<String, Method>();
    //自定视图解析器
    private ViewResolver myViewResolver;

    //启动就会调用此方法,通过启动的时候来进行代码初始化操作
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        //扫描包路径并解析数据结构
        scanController(servletConfig);
        //初始化handler映射
        initHandlerMapping();
        //加载视图解析器,现在应该不用了，以后在实现
        loadViewResolver(servletConfig);
    }

    //加载资源解析器
    public void loadViewResolver(ServletConfig servletConfig) {
        SAXReader saxReader = new SAXReader();
        String path = servletConfig.getServletContext().getRealPath("") + "WEB-INF/classes" + servletConfig.getInitParameter("contextConfigLocation");
        //解析文档
        try {
            Document document = saxReader.read(path);
            Element root = document.getRootElement();
            Iterator iterator = root.elementIterator();
            while (iterator.hasNext()) {
                Element ele = (Element) iterator.next();
                if (ele.getName().equals("bean")) {
                    String className = ele.attributeValue("class");
                    Class clazz = Class.forName(className);
                    Object obj = clazz.newInstance();
                    //获取set方法
                    Method prefixMethod = clazz.getMethod("setPrefix", String.class);
                    Method suffixMethod = clazz.getMethod("setSuffix", String.class);
                    Iterator beanIter = ele.attributeIterator();
                    Map<String, String> propertyMap = new HashMap();
                    while (beanIter.hasNext()) {
                        Element beanEle = (Element) beanIter.next();
                        String name = beanEle.attributeValue("name");
                        String value = beanEle.attributeValue("value");
                        propertyMap.put(name, value);

                    }
                    for (String str : propertyMap.keySet()) {
                        //反射机制调用setter方法，完成赋值。
                        if (str.equals("prefix")) {
                            prefixMethod.invoke(obj, propertyMap.get(str));
                        }
                        if (str.equals("suffix")) {
                            suffixMethod.invoke(obj, propertyMap.get(str));
                        }
                    }
                    myViewResolver = (ViewResolver) obj;
                }
            }
        } catch (Exception e) {
            e.getStackTrace();
        }

    }

    //因为扫描器先执行了,已经解析出来，这里需要一个处理器映射器
    public void initHandlerMapping() {
        //遍历容器
        for (String str : mvcBeanMap.keySet()) {
            //获取对象的class
            Class clazz = mvcBeanMap.get(str).getClass();
            //获取对象的所有方法
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                //方法标注的有RequestMapping标签的
                if (method.isAnnotationPresent(RequestMapping.class)) {
                    //获取这个标签
                    RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                    //获取标签的值
                    String value = annotation.value().substring(1);
                    //method存入methodMapping
                    handlerMapping.put(value, method);
                }
            }
        }
    }

    /**
     * 解析
     *
     * @param servletConfig
     */
    public void scanController(ServletConfig servletConfig) {
        SAXReader reader = new SAXReader();

        try {
            //定位spring-mvc.xml文件位置,这里定位的位置是编译后的，所有看到classes目录不要奇怪
            String path = servletConfig.getServletContext().getRealPath("") + "WEB-INF/classes/" + servletConfig.getInitParameter("contextConfigLocation");
            Document document = reader.read(path);
            //获取到xml的根元素
            Element root = document.getRootElement();
            Iterator iterator = root.elementIterator();
            while (iterator.hasNext()) {
                //强转一下数据
                Element element = (Element) iterator.next();
                //寻找到component-scan 标签
                if (element.getName().equals("component-scan")) {
                    String packageName = element.attributeValue("base-package");
                    //获取base-package包下的所有类名
                    List<String> list = getClassNames(packageName);
                    for (String str : list) {
                        Class clazz = Class.forName(str);
                        //本对象是一个用户定义的控制器对象
                        if (clazz.isAnnotationPresent(Controller.class)) {
                            //获取控制器的RequestMapping注解的value
                            RequestMapping requestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
                            String value = requestMapping.value().substring(1);
                            //把value当成key注册到mvcBeanMap中
                            mvcBeanMap.put(value, clazz.newInstance());
                        }
                    }
                }


            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 获取包路径下所以类名
     */
    public List<String> getClassNames(String scanPath) {
        // 第一个class类的集合
        List<String> classNames = new ArrayList<String>();
        // 是否循环迭代
        boolean recursive = true;
        //在我们运行的时候，包是.来表示,但是文件夹是用/来标识
        String packageDirName = scanPath.replace('.', '/');
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            //循环迭代找文件
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                String protocol = url.getProtocol();
                System.out.println("protocol:" + protocol);
                //如果是保存在本地,
                if ("file".equals(protocol)) {
                    // 获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // 以文件的方式扫描整个包下的文件 并添加到集合中
                    scanClassesInPackageByFile(scanPath, filePath, recursive, classNames);

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classNames;
    }

    /**
     * 以文件的形式来获取包下的所有Class,这里是一个递归操作
     */
    public static void scanClassesInPackageByFile(String scanPath, String packagePath, final boolean recursive,
                                                  List<String> classNames) {
        File dir = new File(packagePath);
        // 如果不存在或者 也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        // 如果存在 就获取包下的所有文件 包括目录
        File[] dirfiles = dir.listFiles(new FileFilter() {
            // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
            @Override
            public boolean accept(File file) {
                return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
            }
        });
        // 循环所有文件
        for (File file : dirfiles) {
            // 如果是目录 则继续扫描
            if (file.isDirectory()) {
                scanClassesInPackageByFile(scanPath + "." + file.getName(), file.getAbsolutePath(), recursive,
                        classNames);
            } else {
                // 如果是java类文件 去掉后面的.class 只留下类名
                String className = file.getName().substring(0, file.getName().length() - 6);

                // 添加到集合中去
                classNames.add(scanPath + '.' + className);

            }
        }
    }

    /**
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        this.doPost(req, resp);
    }

    /**
     * 这里是项目启动之后，前端发起请求，后端的调用链路情况
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求连接
        String handlerUri = req.getRequestURI().split("/")[1];
        Object object = mvcBeanMap.get(handlerUri);
        String methodUri = req.getRequestURI().split("/")[2];
        Method method = handlerMapping.get(methodUri);
        try {
            //反射机制调用业务方法
            String value =(String) method.invoke(object);
            //视图解析器将逻辑视图转换为物理视图
            String result = myViewResolver.jspMapping(value);
            //页面跳转
            req.getRequestDispatcher(result).forward(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
